package com.loopj.ohos.image;

import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.ImagePacker;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;

import java.io.*;
import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WebImageCache {
    private static final HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, "WebImageCache");
    private static final int CONNECT_TIMEOUT = 5000;
    private static final int READ_TIMEOUT = 10000;
    private static final String DISK_CACHE_PATH = "/web_image_cache/";
    /**
     * 缓存地址长度限制
     */
    private static final int CACHE_PATH_LEN = 200;

    /**
     * 考虑到缓存查找的速度问题，在实现内存缓存时一般都会使用类似哈希表这样查找时间复杂度低的数据结构。
     * 由于存在多个线程同时在哈希表中查找的情况，需要考虑多线程并发访问的问题。故使用 ConcurrentHashMap。
     * 内存缓存中我们不会直接持有Bitmap实例的引用，而是通过SoftReference来持有Bitmap对象的软引用，
     * 如果一个对象具有软引用，内存空间足够时，垃圾回收器不会回收它，只有在内存空间不足时，才会回收这些对象占用的内存。
     * 因此，软引用通常用来实现内存敏感的高速缓存。
     */
    private ConcurrentHashMap<String, SoftReference<PixelMap>> memoryCache;
    private String diskCachePath;
    private boolean diskCacheEnabled;
    private ExecutorService writeThread;

    /**
     * 构建两级缓存空间
     *
     * @param context Context
     */
    public WebImageCache(Context context) {
        // Set up in-memory cache store
        memoryCache = new ConcurrentHashMap<String, SoftReference<PixelMap>>();

        // Set up disk cache store
        Context appContext = context.getApplicationContext();
        diskCachePath = appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;
        HiLog.info(label, "WebImageCache diskCachePath =" + diskCachePath);
        File outFile = new File(diskCachePath);
        if (!outFile.exists()) {
            boolean mkdirSuccess = outFile.mkdirs();
            HiLog.info(label, "WebImageCache mkdirSuccess" + mkdirSuccess);
        }

        diskCacheEnabled = outFile.exists();

        // Set up threadpool for image fetching tasks
        writeThread = Executors.newSingleThreadExecutor();
    }

    /**
     * 从Memory获取bitmap实例
     *
     * @param url 图片url
     * @return PixelMap
     */
    public PixelMap get(final String url) {
        PixelMap bitmap;

        // Check for image in memory
        bitmap = getBitmapFromMemory(url);

        // Check for image on disk cache
        if (bitmap == null) {
            bitmap = getBitmapFromDisk(url);
            // Write bitmap back into memory cache
            if (bitmap != null) {
                cacheBitmapToMemory(url, bitmap);
                HiLog.debug(label, "cacheBitmapToMemory");
            }
        } else {
            HiLog.debug(label, "getBitmapFromMemory");
        }

        return null;
    }

    public void put(String url, PixelMap bitmap) {
        cacheBitmapToMemory(url, bitmap);
        cacheBitmapToDisk(url, bitmap);
    }

    public void remove(String url) {
        if (url == null) {
            return;
        }

        // Remove from memory cache
        memoryCache.remove(getCacheKey(url));

        // Remove from file cache
        File f = new File(diskCachePath, getCacheKey(url));
        if (f.exists() && f.isFile()) {
            boolean isDeleteSuccess = f.delete();
            HiLog.debug(label, "isDeleteSuccess" + isDeleteSuccess);
        }
    }

    public void clear() {
        // Remove everything from memory cache
        memoryCache.clear();
        // Remove everything from file cache
        File cachedFileDir = new File(diskCachePath);
        if (cachedFileDir.exists() && cachedFileDir.isDirectory()) {
            File[] cachedFiles = cachedFileDir.listFiles();
            for (File f : cachedFiles) {
                if (f.exists() && f.isFile()) {
                    boolean isDeleteSuccess = f.delete();
                    HiLog.debug(label, "clear isDeleteSuccess" + isDeleteSuccess);
                }
            }
        }
    }

    private void cacheBitmapToMemory(final String url, final PixelMap bitmap) {
        memoryCache.put(getCacheKey(url), new SoftReference<PixelMap>(bitmap));
    }

    private void cacheBitmapToDisk(final String url, final PixelMap bitmap) {
        writeThread.execute(new Runnable() {
            @Override
            public void run() {
                if (diskCacheEnabled) {
                    BufferedOutputStream ostream = null;
                    try {
                        ostream = new BufferedOutputStream(new FileOutputStream(new File(diskCachePath, getCacheKey(url))), 2 * 1024);
                        ImagePacker imagePacker = ImagePacker.create();
                        ImagePacker.PackingOptions packingOptions = new ImagePacker.PackingOptions();
                        packingOptions.quality = 10;
                        imagePacker.initializePacking(ostream, packingOptions);
                    } catch (FileNotFoundException e) {
                        HiLog.debug(label, "cacheBitmapToDisk FileNotFoundException");
                    } finally {
                        try {
                            if (ostream != null) {
                                ostream.flush();
                                ostream.close();
                            }
                        } catch (IOException e) {
                            HiLog.debug(label, "cacheBitmapToDisk IOException");
                        }
                    }
                }
            }
        });
    }

    private PixelMap getBitmapFromMemory(String url) {
        PixelMap bitmap = null;
        //通过memoryCache取出软引用
        SoftReference<PixelMap> softRef = memoryCache.get(getCacheKey(url));
        //判断系统有无回收该引用
        if (softRef != null) {
            bitmap = softRef.get();
        }

        return bitmap;
    }

    /**
     * 从Disk获取Bitmap实例
     *
     * @param url 图片url
     * @return PixelMap
     */
    private PixelMap getBitmapFromDisk(String url) {
        PixelMap bitmap = null;
        if (diskCacheEnabled) {
            String filePath = getFilePath(url);
            File file = new File(filePath);
            if (file.exists()) {
                bitmap = getBitmapFromUrl(filePath);
            }
        }
        return bitmap;
    }

    private PixelMap getBitmapFromUrl(String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            ImageSource imageSource = ImageSource.create(filePath, null);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            decodingOptions.desiredSize = new Size(100, 2000);
            decodingOptions.desiredRegion = new Rect(0, 0, 100, 100);
            decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            return pixelMap;
        }
        return null;
    }

    private String getFilePath(String url) {
        return diskCachePath + getCacheKey(url);
    }

    /**
     * 获取缓存路径
     *
     * @param context context
     * @return 文件路径
     */
    public static String getDiskCachePath(Context context) {
        Context appContext = context.getApplicationContext();
        return appContext.getCacheDir().getAbsolutePath() + DISK_CACHE_PATH;
    }

    public static String getCache(String url) {
        if (url == null) {
            throw new RuntimeException("Null url passed in");
        } else {
            return url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+");
        }
    }

    private String getCacheKey(String url) {
        if (url == null) {
            throw new RuntimeException("Null url passed in");
        } else {
            String path = url.replaceAll("[.:/,%?&=]", "+").replaceAll("[+]+", "+");
            if (path.length() > CACHE_PATH_LEN) {
                return path.substring(0, CACHE_PATH_LEN);
            } else {
                return path;
            }
        }
    }
}
